package dryven.view.engine.component.metadata;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.LinkedList;

import dryven.view.engine.component.MultiThreadViewComponent;
import dryven.view.engine.component.ViewComponent;
import dryven.view.engine.component.annotations.ChildZones;
import dryven.view.engine.component.annotations.ComponentAttribute;
import dryven.view.engine.component.childzone.ChildZoneStrategy;
import dryven.view.engine.expression.ViewDataDeclaration;
import dryven.view.engine.expression.nodelisters.ExpressionNodeFactory;
import dryven.view.engine.parser.CompilationException;
import dryven.view.engine.parser.ViewComponentLocator;
import dryven.view.engine.parser.ViewDataDeclarationCollection;
import dryven.view.engine.parser.componentinvocation.AttributeBindings;

public class MultiThreadStrategy implements ViewComponentMetaDataStrategy {

	private Class<? extends MultiThreadViewComponent> _componentClass;
	private ChildZoneStrategy _childZoneStrategy;
	
	public MultiThreadStrategy(
			Class<? extends MultiThreadViewComponent> componentClass) {
		super();
		_componentClass = componentClass;
	}

	@Override
	public Iterable<AttributeDeclaration> getAttributeDeclarations() {
		Method m = new MultiThreadRenderMethodDiscoverer()
				.getRenderMethod(_componentClass);
		LinkedList<AttributeDeclaration> attrs = new LinkedList<AttributeDeclaration>();
		for (int i = 0; i < m.getParameterTypes().length; ++i) {
			Annotation[] annos = m.getParameterAnnotations()[i];
			Class<?> type = m.getParameterTypes()[i];
			for (Annotation annotation : annos) {
				if (annotation.annotationType()
						.equals(ComponentAttribute.class)) {
					ComponentAttribute attrName = (ComponentAttribute) annotation;
					attrs.add(new AttributeDeclaration(type, attrName.value(),
							attrName.required()));
					break;
				}
			}
		}
		return attrs;
	}

	@Override
	public Iterable<String> getChildZonesForComponent() {
		loadChildZoneStrategy();
		return _childZoneStrategy.getChildZones();
	}

	private void loadChildZoneStrategy() {
		Class<?> type = _componentClass;
		ChildZones czAnno = null;
		while (czAnno == null) {
			
			if (type.equals(ViewComponent.class)) {
				throw new RuntimeException(
						String.format("@%s required on view component %s when view component strategy %s is used.",
							ChildZones.class.getName(),
							_componentClass.getName(), 
							getClass().getName()
						)
				);
			}
			
			for (Annotation anno : type.getDeclaredAnnotations()) {
				if(anno instanceof ChildZones) {
					czAnno = (ChildZones) anno;
				}
			}
			type = type.getSuperclass();
		}
		Class<? extends ChildZoneStrategy> strategyClass = czAnno.strategy();
		ChildZoneStrategy strategy = null;
		try {
			Constructor<? extends ChildZoneStrategy> ctor = strategyClass.getConstructor(new Class<?>[]{});
			boolean accessible = ctor.isAccessible();
			try {
				ctor.setAccessible(true);
				strategy = ctor.newInstance(new Object[]{});
			} finally {
				ctor.setAccessible(accessible);
			}
		} catch (Exception e) {
			throw new RuntimeException(String.format("%s subclass %s needs a default constructor",
					ChildZoneStrategy.class.getName(),
					strategyClass.getName()
				)
				,e
			);
		}
		_childZoneStrategy = strategy;
	}

	@Override
	public Iterable<ViewDataDeclaration> getImplicitDeclarationsForChildZone(
			ExpressionNodeFactory factory, String name, AttributeBindings attributeBindings) {
		return _childZoneStrategy.getViewDataDeclarations(name, attributeBindings, factory);
	}

	@Override
	public ViewComponent getComponentInstance() {
		try {
			return _componentClass.newInstance();
		} catch (Exception e) {
			throw new RuntimeException(String.format("View component %s uses the strategy %s and does not have a public default constructor.", 
					_componentClass.getName(), 
					getClass().getName()),
				e);
		}
	}

	@Override
	public void compile(ViewDataDeclarationCollection declarations,
			ViewComponentLocator locator) throws CompilationException {
		// TODO Auto-generated method stub

	}

	@Override
	public String getComponentName() {
		return _componentClass.getSimpleName();
	}
}
